Skip to content

Add thread-safe Steihaug, BOBYQA and NEWUOA trust-region optimizers - #1

Merged
mattfidler merged 5 commits into
mainfrom
steihaug
Sep 17, 2026
Merged

mattfidler merged 5 commits into
mainfrom
steihaug

Conversation

@mattfidler

Copy link
Copy Markdown
Member

Adds three trust-region optimizers. Like trust(), each has an R function and a thread-safe C entry point with no R API calls and no global state, so it can be used as an inner or outer optimizer inside nlmixr2est's parallel C++ code.

R function Ported from Needs C entry point
steihaug() Rust crate basin (commit 86cc0d6) gradient + Hessian, or Hessian-vector products steihaug_solve_c()
bobyqa() minqa 1.2.8 (Powell's Fortran) function values; box bounds bobyqa_solve_c()
newuoa() minqa 1.2.8 (Powell's Fortran) function values newuoa_solve_c()

UOBYQA is not included: it needs O(n²) points, and NEWUOA covers the same case.

How closely the ports match

  • BOBYQA and NEWUOA are line-by-line translations. Results are bitwise identical to minqa's Fortran on x86_64 (par, fval, feval, ierr and iprint output) across 250+ comparison runs plus 300 random ones.
    • The R wrappers reproduce minqa's R/minqa.R semantics.
    • One deliberate difference: minqa's rescue.f calls CALFUN(N,X,IPRINT) with X undefined inside RESCUE, so it evaluates the objective at uninitialized memory and results aren't repeatable. The port uses Powell's intended W(1..N). Runs that enter RESCUE match a copy of minqa with only that line fixed.
  • Steihaug matches a basin reference program bit for bit on 33 setups, each in exact and matrix-free mode: every evaluation point, iteration count, termination reason and evaluation count.
    • Defaults that differ from basin: maxit = 100 and an absolute gradient tolerance of 1e-8.
  • FMA contraction is disabled in the ported files, so the bitwise agreement doesn't depend on compiler flags.

Downstream interface

  • .RcppTrustPtr() and inst/include/RcppTrust.h gain appended slots: bobyqa_solve_c, newuoa_solve_c, minqa_result_free, steihaug_solve_c, steihaug_result_free. Existing slots are unchanged.
  • When a consumer was built against a newer header than the installed RcppTrust, the missing slots are NULL.

Other changes

  • License is now GPL-2, because the minqa-derived code can't be distributed under MIT. The notices for the trust and basin code are kept in inst/COPYRIGHTS.
  • Version bumped to 0.2.0; NEWS.md added.
  • NAMESPACE is now roxygen-managed.
  • New vignette, vignette("trust-region-methods"); README and the main vignette updated.

Testing

  • tests/testthat/test-minqa.R: exact comparison with minqa when it is installed (approximate on non-x86_64), error codes, R error propagation.
  • tests/testthat/test-steihaug.R: basin reference counts, matrix-free vs exact, termination reasons, input validation.
  • OpenMP stress tests: each solver gives bitwise-identical results on 64 starts, run sequentially and across threads.
  • The C interface's edge cases (NULL options, zero start, bad n) were run under AddressSanitizer and UBSan.
  • R CMD check --as-cran: 1 NOTE, a non-portable compiler flag from this machine's R configuration.

- steihaug(): Steihaug truncated-CG trust-region Newton minimizer ported
  from the Rust crate basin (exact-Hessian and matrix-free modes), with a
  thread-safe C entry point steihaug_solve_c(). Matches basin bit for bit.
- bobyqa()/newuoa(): line-by-line C++ ports of Powell's BOBYQA/NEWUOA from
  minqa 1.2.8, drop-in replacements for the minqa R functions, with
  thread-safe C entry points. Bitwise identical to minqa; BOBYQA's RESCUE
  step evaluates the intended point instead of minqa's uninitialized one.
- Append new slots to .RcppTrustPtr()/RcppTrust.h for nlmixr2est.
- OpenMP thread-safety tests, fidelity tests against minqa and basin
  reference counts, new vignette "trust-region-methods".
- NAMESPACE is now roxygen-managed.
- License changed to GPL-2 (minqa-derived code); trust and basin notices
  retained in inst/COPYRIGHTS. Version 0.2.0.
@codecov

codecov Bot commented Sep 17, 2026

Copy link
Copy Markdown

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

Thanks for integrating Codecov - We've got you covered ☂️

- On non-x86_64, don't compare end points of the ill-conditioned
  tight-bounds BOBYQA case (FMA-contracted Fortran stops elsewhere).
- Skip the iprint text comparison on Windows, where Rprintf and C++
  snprintf format %#g differently.
- Cover NEWUOA's BIGDEN and BOBYQA's RESCUE paths, every Steihaug
  stopping rule, R wrapper argument handling, and the C entry points'
  invalid-input and callback-failure codes (coverage 75.9% -> 92.0%).
- Ill-conditioned tight-bounds BOBYQA case: on non-x86_64 compare only the
  result class (minqa's FMA-contracted Fortran exits with ierr 3 there).
- Trigger the non-finite-x abort with a NaN start instead of an unbounded
  run; check the unbounded ierr = 3 exit only on x86_64.
- Loosen the RESCUE test's accuracy threshold; assert RelativeCostTolerance
  and MaxIter explicitly.
- Add M. J. D. Powell as author (Michael J. D. Powell) and cite his
  original BOBYQA/NEWUOA Fortran 77 sources (PRIMA archive) and papers in
  DESCRIPTION, man pages, README, vignette and inst/COPYRIGHTS.
- License stays GPL-2: the C++ ports are translated from minqa's modified
  Fortran and R/minqa.R and the print glue follow minqa's R/C++ code, so
  the minqa authors remain listed as contributors/copyright holders.
@mattfidler
mattfidler merged commit 38fd778 into main Sep 17, 2026
9 checks passed
@mattfidler
mattfidler deleted the steihaug branch September 17, 2026 21:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant